package io.github.openfeign.spring.enhancer;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import lombok.extern.slf4j.Slf4j;

/**
 * Feign 文件流式写入字节码转换器
 *
 * <p>修改 {@code feign.form.spring.SpringSingleMultipartFileWriter} 的 write 方法，
 * 将原始的一次性内存加载改为分块流式写入，解决大文件上传时的内存溢出问题。</p>
 *
 * <h3>改造对比</h3>
 *
 * <h4>原始实现（内存加载模式）:</h4>
 * <pre>{@code
 * protected void write(Output output, String key, Object value) {
 *     MultipartFile file = (MultipartFile) value;
 *     writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType());
 *     byte[] bytes = file.getBytes(); // 一次性加载文件内容
 *     output.write(bytes);
 * }
 * }</pre>
 *
 * <h4>优化后实现（流式处理）:</h4>
 * <pre>{@code
 * protected void write(Output output, String key, Object value) {
 *     MultipartFile file = (MultipartFile) value;
 *     writeFileMetadata(output, key, file.getOriginalFilename(), file.getContentType());
 *     byte[] buffer = new byte[4096]; // 4KB缓冲区
 *     InputStream is = file.getInputStream();
 *     try {
 *         int bytesRead = is.read(buffer);
 *         while (bytesRead > 0) {
 *             output.write(buffer, 0, bytesRead); // 分块写入
 *             bytesRead = is.read(buffer);
 *         }
 *     } finally {
 *         is.close();
 *     }
 * }
 * }</pre>
 *
 */
@Slf4j
public class SpringSingleMultipartFileWriterBytecodeTransformer extends AbstractBytecodeTransformer {

    private static final String TARGET_CLASS_NAME = "feign.form.spring.SpringSingleMultipartFileWriter";
    private static final String TARGET_METHOD_NAME = "write";

    private final long bufferSize;

    public SpringSingleMultipartFileWriterBytecodeTransformer(final ClassPool classPool, final String classOutPath, final long bufferSize) {
        super(classPool, classOutPath);
        this.bufferSize = bufferSize;
    }

    /**
     * {@inheritDoc}
     */
    public void transformBytecode() throws NotFoundException, CannotCompileException {

        final CtClass ctClass = getCtClass();
        if (ctClass == null) {
            return;
        }
        if(log.isDebugEnabled()){
            log.debug("Start transforming {}#{}", TARGET_CLASS_NAME, TARGET_METHOD_NAME);
        }

        final CtMethod method = ctClass.getDeclaredMethod(
                TARGET_METHOD_NAME,
                new CtClass[]{classPool.get("feign.form.multipart.Output"), classPool.get("java.lang.String"), classPool.get("java.lang.Object")}
        );

        method.setBody("{\n" +
                "            org.springframework.web.multipart.MultipartFile file = (org.springframework.web.multipart.MultipartFile) $3;\n" +
                "            writeFileMetadata($1, $2, file.getOriginalFilename(), file.getContentType());\n" +
                "            byte[] buf = new byte[" + bufferSize + "];\n" +
                "            java.io.InputStream inputStream = file.getInputStream(); \n" +
                "            try{\n" +
                "                int length = inputStream.read(buf);\n" +
                "                while (length > 0) {\n" +
                "                    $1.write(buf, 0, length);\n" +
                "                    length = inputStream.read(buf);\n" +
                "                }\n" +
                "            } catch (java.io.IOException e) {\n" +
                "                throw new java.lang.RuntimeException(\"An error occurred while writing data to the output stream. " +
                "This may be caused by a closed stream, insufficient permissions, or an unexpected I/O interruption.\", e);\n" +
                "            }finally{\n" +
                "                try {\n" +
                "                    inputStream.close();\n" +
                "                } catch (java.io.IOException e) {\n" +
                "                }\n" +
                "            }\n" +
                "        }");

        redefineClassInMemory(ctClass);
        if(log.isDebugEnabled()){
            log.debug("Successfully transformed {}#{}", TARGET_CLASS_NAME, TARGET_METHOD_NAME);
        }
    }

    private CtClass getCtClass() {
        try {
            return classPool.get(TARGET_CLASS_NAME);
        } catch (final NotFoundException e) {
            // 没有引入feign-form-spring，忽略
            log.warn("Class not found : feign.form.spring.SpringSingleMultipartFileWriter, feign-form-spring is not introduced. ", e);
        }
        return null;
    }
}
